<template>
  <dialog ref="dialogRef" class="entry-dialog redesigned" @close="emit('close')" @keydown.stop="onInnerKeydown">
    <form method="dialog" class="dialog-shell" @submit.prevent="save" aria-labelledby="entryDialogTitle">
      <div class="dialog-header">
        <h3 id="entryDialogTitle" class="dialog-title">
          <slot name="title-icon">
            <svg class="dialog-title-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
              <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
              <line x1="8" y1="21" x2="16" y2="21" />
              <line x1="12" y1="17" x2="12" y2="21" />
            </svg>
          </slot>
          {{ isEdit ? '编辑条目' : '新增条目' }}
        </h3>
        <button type="button" class="ui-close-btn fancy-rotate" @click="close" aria-label="关闭">
          <svg class="icon-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
            <line x1="18" y1="6" x2="6" y2="18" />
            <line x1="6" y1="6" x2="18" y2="18" />
          </svg>
        </button>
      </div>
      <div class="dialog-content-scroll">
        <section class="form-section-card base-info" aria-labelledby="baseInfoTitle">
          <header class="section-title-row">
            <h4 id="baseInfoTitle" class="section-title">基础信息</h4>
            <p class="section-sub">填写生成成本条目的核心标识字段。</p>
          </header>
          <div class="section-body grid-two" role="group" aria-label="基础信息字段">
            <label class="field-block">名称
              <!-- 新增模式下自动生成，禁用手工输入；编辑模式保留原值只读 -->
              <input v-model="form.costName" :disabled="true" class="readonly" required :title="autoNameHint" />
            </label>
            <label class="field-block">BOM类型
              <template v-if="!isEdit">
                <template v-if="props.lockBomType">
                  <input :value="formatBomType(form.bomType)" disabled class="readonly" />
                </template>
                <template v-else>
                  <select v-model="form.bomType" required>
                    <option value="product">物料BOM</option>
                    <option value="package">包装BOM</option>
                  </select>
                </template>
              </template>
              <template v-else>
                <input :value="formatBomType(form.bomType)" disabled class="readonly" />
              </template>
            </label>
            <label class="field-block">归属产品类型
              <template v-if="!isEdit">
                <template v-if="props.lockProductType">
                  <input :value="form.productType" disabled class="readonly" />
                </template>
                <template v-else>
                  <select v-model="form.productType" required>
                    <option value="" disabled>请选择产品类型</option>
                    <option v-for="opt in productTypeOptions" :key="opt" :value="opt">{{ opt }}</option>
                  </select>
                </template>
              </template>
              <template v-else>
                <input :value="form.productType" disabled class="readonly" />
              </template>
            </label>
            <label class="field-block">物料分类
              <template v-if="!isEdit">
                <select v-model="form.materialCategory" required @change="onCategoryChange">
                  <option value="" disabled>请选择分类</option>
                  <option v-for="c in categoryOptions" :key="c.code" :value="c.code">{{ c.name }}</option>
                </select>
              </template>
              <template v-else>
                <input :value="displayCategoryName(form.materialCategory)" disabled class="readonly" />
              </template>
            </label>
          </div>
        </section>
        <section class="form-section-card cost-config" aria-labelledby="costConfigTitle">
          <header class="section-title-row">
            <h4 id="costConfigTitle" class="section-title">费用配置</h4>
            <p class="section-sub" v-if="form.costMode==='fixed'">当前模式：固定费用，直接按金额计入。</p>
            <p class="section-sub" v-else>当前模式：计数费用，后续按数量 × 单价。</p>
          </header>
          <div class="cost-config-bar">
            <div class="mode-radios" role="radiogroup" aria-label="费用模式">
              <button type="button" class="pill-radio" :class="{ active: form.costMode==='fixed', fixed: form.costMode==='fixed' }" @click="form.costMode='fixed'" :aria-pressed="form.costMode==='fixed'">固定费用</button>
              <button type="button" class="pill-radio" :class="{ active: form.costMode==='count', count: form.costMode==='count' }" @click="form.costMode='count'" :aria-pressed="form.costMode==='count'">计数费用</button>
            </div>
            <div class="amount-focus inline" :class="form.costMode==='count' ? 'mode-count' : 'mode-fixed'" aria-live="polite">
              <span class="focus-label">{{ form.costMode==='fixed' ? '金额' : '单价' }}</span>
              <span class="focus-value">{{ Number(form.amount || 0).toFixed(2) }}</span>
            </div>
          </div>
          <div class="amount-row single-line">
            <label class="field-block amount-field">{{ form.costMode==='fixed' ? '固定金额' : '单价' }}
              <div class="input-affix">
                <span class="prefix" aria-hidden="true">¥</span>
                <input type="number" step="0.1" min="0" v-model.number="form.amount" required />
              </div>
            </label>
          </div>
        </section>
        <section class="form-section-card attr-config" aria-labelledby="attrConfigTitle">
          <header class="section-title-row">
            <h4 id="attrConfigTitle" class="section-title">属性设置</h4>
            <p class="section-sub">可配置规格(宽×高)；后续可扩展多个维度。</p>
          </header>
          <div class="attr-config-body single">
            <button type="button" class="attr-mini-btn" :class="{ 'filled': !!form.attributeSpec, disabled: !form.materialCategory, readonly: isEdit }" @click="onAttrBtnClick" :disabled="!form.materialCategory">
              <slot name="attr-icon">
                <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                  <path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" />
                  <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06A2 2 0 1 1 2.17 17.3l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H1a2 2 0 1 1 0-4h.09c.67 0 1.26-.38 1.51-1a1.65 1.65 0 0 0-.33-1.82L2.2 7.4A2 2 0 1 1 5.03 4.57l.06.06c.5.49 1.17.73 1.82.33.59-.36.98-.92 1-1.51V3a2 2 0 1 1 4 0v.09c.02.59.41 1.15 1 1.51a1.65 1.65 0 0 0 1.82-.33l.06-.06A2 2 0 1 1 17.8 7.4l-.06.06c-.49.5-.73 1.17-.33 1.82.36.59.92.98 1.51 1H19a2 2 0 1 1 0 4h-.09c-.67 0-1.26.38-1.51 1Z" />
                </svg>
              </slot>
              <span>{{ isEdit ? '查看属性' : (form.attributeSpec ? '编辑属性' : '属性设置') }}</span>
            </button>
            <span v-if="form.materialCategory && attributeDefinitions.length" class="attr-summary">已定义 {{ attributeDefinitions.length }} 项属性</span>
          </div>
        </section>
      </div>
      <div class="dialog-actions sticky-actions no-hint">
        <div class="actions-btns full">
          <button class="btn secondary" value="cancel" type="button" @click="close">取消</button>
          <button class="btn primary" type="submit" :disabled="!canSubmit">保存</button>
        </div>
              <p v-if="saveError" class="error-line" role="alert">{{ saveError }}</p>
      </div>
    </form>
  </dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import type { MaterialAttributeDefinition } from '../mock/materialAttributes';
import { getMaterialClass, getPropertiesWithItemsByClassId } from '../../../services/menu';
import { addBom } from '../../../services/bom';
import type { BomAttributeItem } from '../../../types/bom';
interface SimpleBomEntryLike { id?:string; costName:string; bomType:'product'|'package'; productType?:string; materialCode?:string; materialCategory:string; costMode:'fixed'|'count'; fixedAmount?:number; unitPrice?:number; attributeSpec?:string; attributeDefinitions?:MaterialAttributeDefinition[]; attributeValues?:Record<string,any>; }
const dialogRef = ref<HTMLDialogElement|null>(null);
const current = ref<SimpleBomEntryLike|null>(null);
const form = ref({ costName:'', bomType:'product' as 'product'|'package', productType:'', materialCode:'', materialCategory:'', costMode:'fixed' as 'fixed'|'count', amount:0, attributeSpec:'', attributeDefinitions:[] as MaterialAttributeDefinition[], attributeValues:{} as Record<string,any> });
const categoryOptions = ref<{code:string;name:string;id?:number}[]>([]);
// 使用后端接口获取物料分类，选项的 value 为返回的 Id（字符串形式），展示使用 MaterialClass
getMaterialClass().then(list => {
  categoryOptions.value = (list || []).map(item => ({ code: String(item.Id), name: item.MaterialClass, id: item.Id }));
}).catch(() => {
  // 如果接口失败，保持空数组（或可回退到 mock 数据）
});
const attributeDefinitions = ref<MaterialAttributeDefinition[]>([]);
const attributeValues = ref<Record<string,any>>({});
const productTypeOptions = ['通用','户外花箱','户外围栏','户外拼接地板','吸音板','弯曲墙板','墙裙','组合建材','Pre-Cut'];
// 修改编辑模式判定：原逻辑依据是否有 id，自动生成的框条行 id 为空字符串导致被判定为“新增”从而出现分类等字段可编辑。
// 需求：自动生成的框条行（以及任何传入的现有行）在点击“编辑”时除费用配置外其余字段只读，因此只要传入了 row 就视为编辑。
const isEdit = computed(()=> current.value !== null);
const canSubmit = computed(()=> form.value.costName && form.value.materialCategory && form.value.productType && form.value.amount>=0);
const emit = defineEmits<{(e:'save', payload:any):void; (e:'close'):void; (e:'request-attr-edit', payload:{ categoryCode:string; definitions:MaterialAttributeDefinition[]; values:Record<string,any>; materialName:string; }):void; (e:'request-attr-view', payload:{ attributeSpec?:string; definitions?:MaterialAttributeDefinition[]; values?:Record<string,any>; categoryCode?:string; materialName:string; }):void}>();
// 控制是否将“重复名称”错误视为成功（仅产品编辑页面使用）。默认 false：仍提示错误。
const props = defineProps<{ duplicateGraceful?: boolean; presetBomType?: 'product'|'package'; presetProductType?: string; lockBomType?: boolean; lockProductType?: boolean }>();
function displayCategoryName(code:string){ return categoryOptions.value.find(c=>c.code===code)?.name || code; }
async function onCategoryChange(){
  attributeDefinitions.value=[]; attributeValues.value={};
  form.value.attributeDefinitions=[]; form.value.attributeValues={}; form.value.attributeSpec='';
  if(!form.value.materialCategory) return;
  // materialCategory 存储为分类 Id 的字符串（来自 getMaterialClass 的 Id 字段）
  const classId = Number(form.value.materialCategory);
  if (!Number.isFinite(classId) || classId <= 0) {
    // 不再回退到本地 mock，直接清空（或可提示用户刷新/联系管理员）
    attributeDefinitions.value = [];
    form.value.attributeDefinitions = [];
    return;
  }
  // 调用后端接口，根据返回结构映射为前端的 MaterialAttributeDefinition
  try {
    const props = await getPropertiesWithItemsByClassId(classId);
    const mapped = (props || []).map(p => {
      // 标准化 controlType（后端可能返回 CheckBox/Checkbox 等）
      const rawType = String(p.controlType || '').toLowerCase();
      let normalizedControl: string = p.controlType;
      if(rawType === 'checkbox' || rawType === 'check_box') normalizedControl = 'checkbox';
      const def: any = {
        // 使用后端 property id 保证唯一
        code: `p_${p.id}`,
        name: p.propertyName,
        control: normalizedControl as any,
        required: !p.allowNull,
        // suffix 不是单位，仅作后缀显示（不要把 suffix 当作单位）
        suffix: p.suffix == null ? '' : p.suffix, // 不做 trim
        placeholder: undefined,
        remark: undefined,
        // 简单分组：数值控件归为 size，否则 basic
        group: p.controlType === 'NumericTextBox' ? 'size' : 'basic',
        prefix: p.prefix == null ? '' : p.prefix, // 不做 trim
        sortId: p.sortId,
        keepZero: p.keepZero,
        decimalDigits: p.decimalDigits
      };
      // 如果有下拉项，直接附带到定义上，组件会优先使用
      if (Array.isArray((p as any).selectItems) && (p as any).selectItems.length) {
        def.selectItems = (p as any).selectItems.map((si: any) => ({ value: String(si.id), label: si.itemName, omit: !!si.omit }));
      }
      return def as MaterialAttributeDefinition;
    });
    attributeDefinitions.value = mapped;
    form.value.attributeDefinitions = mapped;
  } catch (e) {
    // 出错时回退到 mock
  // 后端失败时不再使用本地 mock；保持为空
  attributeDefinitions.value = [];
  form.value.attributeDefinitions = [];
  }
}
function buildAutoName(){
  // 仅在新增模式生成名称
  if(isEdit.value) return;
  const defs:any[] = form.value.attributeDefinitions || [];
  const values = form.value.attributeValues || {};
  if(!defs.length) { form.value.costName=''; return; }
  // 按 sortId 升序（无则置大值）
  const sorted = defs.slice().sort((a,b)=>{
    const sa = typeof a.sortId === 'number'? a.sortId : 99999;
    const sb = typeof b.sortId === 'number'? b.sortId : 99999;
    if(sa!==sb) return sa-sb; return String(a.propertyName||a.name||'').localeCompare(String(b.propertyName||b.name||''),'zh-CN');
  });
  const parts:string[] = [];
  for(const d of sorted){
    // omit 跳过
    if(d.omit) continue;
    let raw = values[d.code];
    const isCheckboxOld = d.controlType==='checkbox' || d.control==='checkbox';
    if(!isCheckboxOld && (raw==null || raw==='')) continue;
    if(isCheckboxOld){
      if(Array.isArray(raw)){
        raw = raw.length>0 ? '有' : '无';
      } else {
        raw = raw ? '有' : '无';
      }
    } else if(d.controlType==='DropDownList' || d.control==='DropDownList') {
      const mapItems = (d.selectItems||[]) as any[];
      const found = mapItems.find(mi=> String(mi.id)===String(raw) || String(mi.value)===String(raw));
      if(found?.omit) continue;
      raw = found ? (found.itemName || found.label || raw) : raw;
      raw = String(raw).trim();
      if(raw==='') continue;
    } else if(d.controlType==='TextBox' || d.control==='TextBox') {
      raw = String(raw).trim();
      if(raw==='') continue;
    } else if(d.controlType==='NumericTextBox' || d.control==='NumericTextBox') {
      const num = Number(raw);
      if(!Number.isFinite(num)) continue;
      if(d.keepZero){
        const digits = (typeof d.decimalDigits === 'number' && d.decimalDigits >= 0) ? d.decimalDigits : 0;
        raw = num.toFixed(digits);
      } else {
        raw = String(raw);
      }
    }
    const prefix = d.prefix == null ? '' : d.prefix;
    const suffix = d.suffix == null ? '' : d.suffix;
    const seg = `${prefix}${raw}${suffix}`;
    if(seg) parts.push(seg);
  }
  form.value.costName = parts.join('');
}

async function onAttrBtnClick(){
  if(!form.value.materialCategory) return;
  if(isEdit.value){
    // materialName 使用物料分类的 MaterialClass 名称（而不是条目自动生成的 costName）
    emit('request-attr-view', { attributeSpec: form.value.attributeSpec, definitions: form.value.attributeDefinitions, values: form.value.attributeValues, categoryCode: form.value.materialCategory, materialName: displayCategoryName(form.value.materialCategory) });
    return;
  }
  // 新增模式：如果还未加载 definitions 则加载
  if(!attributeDefinitions.value.length){
    if(form.value.materialCategory){
  attributeDefinitions.value = [];
  form.value.attributeDefinitions = [];
    }
  }
  emit('request-attr-edit', { categoryCode: form.value.materialCategory, definitions: form.value.attributeDefinitions, values: form.value.attributeValues, materialName: displayCategoryName(form.value.materialCategory) });
}
function open(row?:SimpleBomEntryLike){
  if(row){
    current.value = row;
    form.value = { costName: row.costName, bomType: row.bomType, productType: row.productType||'', materialCode: row.materialCode||'', materialCategory: row.materialCategory, costMode: row.costMode, amount: row.costMode==='fixed' ? (row.fixedAmount||0) : (row.unitPrice||0), attributeSpec: row.attributeSpec||'', attributeDefinitions: row.attributeDefinitions||[], attributeValues: row.attributeValues||{} };
    attributeDefinitions.value = form.value.attributeDefinitions; attributeValues.value = { ...form.value.attributeValues };
  } else {
    current.value = null;
    form.value = { costName:'', bomType:'product', productType:'', materialCode:'', materialCategory:'', costMode:'fixed', amount:0, attributeSpec:'', attributeDefinitions:[], attributeValues:{} };
    attributeDefinitions.value=[]; attributeValues.value={};
    if (props.presetBomType) form.value.bomType = props.presetBomType;
    if (props.presetProductType) form.value.productType = props.presetProductType;
  }
  dialogRef.value?.showModal();
}
function close(){ dialogRef.value?.close(); }
const saveError = ref('');
async function save(){
  saveError.value='';
  if(!canSubmit.value) return; const now=Date.now();
  if(current.value){
    // 编辑模式：不直接修改父级 entries 中的对象，避免“前端先改”导致与后端不一致；仅回传编辑后的必要字段，交由页面统一调 /api/bom/edit 后刷新。
    const edited = {
      id: current.value.id,
      classId: (current.value as any).classId, // 透传后端分类 Id，用于 /api/bom/edit
      costName: current.value.costName, // 名称在编辑模式保持原值（当前需求不允许修改）
      bomType: current.value.bomType,
      productType: current.value.productType,
      materialCode: current.value.materialCode,
      materialCategory: current.value.materialCategory, // 保留名称（或显示值）
      costMode: form.value.costMode,
      fixedAmount: form.value.costMode==='fixed' ? form.value.amount : undefined,
      unitPrice: form.value.costMode==='count' ? form.value.amount : undefined,
      attributeSpec: current.value.attributeSpec,
      attributeDefinitions: current.value.attributeDefinitions,
      attributeValues: current.value.attributeValues
    };
    emit('save', edited);
    close();
    return;
  }
  // 新增逻辑
  buildAutoName();
  const defs = form.value.attributeDefinitions || [];
  const vals = form.value.attributeValues || {};
  // 修复：正确处理属性值的键映射
  const attributes: BomAttributeItem[] = defs.map(d => {
    const propCode = d.code || d.name;
    let propValue: any = '';
    // 优先使用 code 查找，再尝试用 name
    if(propCode && propCode in vals) {
      propValue = vals[propCode];
    } else if(d.name && d.name in vals) {
      propValue = vals[d.name];
    }
    return {
      propertyName: d.name || d.code,
      propertyValue: propValue == null ? '' : propValue,
      sort: typeof (d as any).sortId === 'number' ? (d as any).sortId : undefined
    };
  });
  const classId = form.value.materialCategory;
  const className = displayCategoryName(form.value.materialCategory);
  const createBody = {
    classId,
    className,
    productType: form.value.productType,
    name: form.value.costName,
    bomType: form.value.bomType,
    attributes,
    feeModel: form.value.costMode,
    feeAmount: form.value.amount
  };
  let duplicateDetected = false;
  let returnedData: any = null;
  try {
    returnedData = await addBom(createBody as any);
  } catch (e:any) {
    const msg = e?.message || '';
    const isDuplicate = /Bom exists with the same name/i.test(msg);
    if(isDuplicate && props.duplicateGraceful){
      duplicateDetected = true; // 仅在允许容错的父场景视为成功
      console.warn('检测到重复名称，按容错策略视为成功:', msg);
      // 重复时后端也会返回 data（包含已有 BOM 的 id，直接是数字）
      returnedData = (e as any).data ?? returnedData;
    } else {
      // 任何未被容错策略接受的错误（包括重复但未启用容错）直接显示
      saveError.value = msg || '新增失败';
      return;
    }
  }
  // 构建本地临时对象（等待后端刷新覆盖）
  // 注意：returnedData 直接是 BOM 的 ID（字符串，避免精度丢失），不是对象，所以直接使用
  const localId = returnedData || undefined;
  const categoryClassName = displayCategoryName(form.value.materialCategory);
  // 重要：需要传递 classId 用于后续保存时正确映射分类信息
  const local:any = { 
    id: localId, 
    classId: Number(form.value.materialCategory) || undefined, // 分类 ID
    createdAt: now, 
    updatedAt: now, 
    costName: form.value.costName, 
    bomType: form.value.bomType, 
    productType: form.value.productType||undefined, 
    materialCode: undefined, 
    materialCategory: form.value.materialCategory, 
    className: categoryClassName, 
    costMode: form.value.costMode, 
    useFormula:false, 
    fixedAmount: form.value.costMode==='fixed'? form.value.amount: undefined, 
    unitPrice: form.value.costMode==='count'? form.value.amount: undefined, 
    attributeSpec: form.value.attributeSpec||undefined, 
    attributeDefinitions: form.value.attributeDefinitions, 
    attributeValues: form.value.attributeValues 
  };
  if(duplicateDetected){ local.__duplicateName = true; }
  emit('save', local);
  close();
}
// 已移除自动生成物料编码逻辑，编码由后端负责生成与回填
function formatBomType(t:'product'|'package'){ return t==='package' ? '包装BOM' : '物料BOM'; }
const autoNameHint = '根据所选属性自动拼接 (prefix+值+suffix, 按 sortId 升序, omit 跳过)';
function updateAttributes(values:Record<string,any>, spec:string){
  form.value.attributeValues = { ...values };
  form.value.attributeSpec = spec;
  buildAutoName();
}
defineExpose({ open, close, updateAttributes });

function onInnerKeydown(e: KeyboardEvent){
  // 阻断 ESC 冒泡到父级产品编辑弹窗，避免同时关闭两层
  if(e.key === 'Escape'){
    // 仅阻止冒泡，让原生 cancel 行为（关闭当前 dialog）仍执行
    e.stopPropagation();
  }
}
</script>
<style scoped>
/* 复用页面里的关键样式（裁剪精简版） */
.entry-dialog::backdrop { background:rgba(15,23,42,.55); }
.entry-dialog { border:none; padding:0; border-radius:20px; width:760px; max-width:96vw; box-shadow:0 14px 34px -10px rgba(15,23,42,.25),0 6px 18px -6px rgba(15,23,42,.16); overflow:hidden; }
/* shell 取消强制内部滚动，默认展开全部内容；仅在低窗口高度时回退滚动 */
.dialog-shell { display:flex; flex-direction:column; width:100%; background:linear-gradient(145deg,#ffffff,#f8fafc 60%,#f1f5f9); }
.dialog-header { display:flex; justify-content:space-between; align-items:center; padding:1rem 1.6rem .85rem; border-bottom:1px solid #e2e8f0; background:linear-gradient(135deg,#ffffff,#f8fafc); }
.dialog-title { margin:0; font-size:20px; font-weight:600; display:flex; align-items:center; gap:.6rem; letter-spacing:.5px; }
.dialog-title-icon { width:24px; height:24px; color:#3b82f6; }
/* 默认无滚动条展示所有 section；小屏再启用滚动 */
.dialog-content-scroll { padding:1.1rem 1.6rem 1.25rem; display:flex; flex-direction:column; gap:1.15rem; }
.form-section-card { border:1px solid #e2e8f0; background:linear-gradient(135deg,#ffffff,#f8fafc); border-radius:16px; padding:1rem 1.15rem 1.2rem; box-shadow:0 6px 18px -6px rgba(15,23,42,.14),0 2px 6px -2px rgba(15,23,42,.08); display:flex; flex-direction:column; gap:.9rem; }
.section-title-row { display:flex; flex-wrap:wrap; align-items:center; gap:.8rem; }
.section-title { margin:0; font-size:15px; font-weight:600; letter-spacing:.02em; position:relative; padding-left:.75rem; }
.section-title:before { content:""; position:absolute; left:0; top:50%; transform:translateY(-50%); width:3px; height:70%; background:linear-gradient(135deg,#3b82f6,#8b5cf6); border-radius:2px; }
/* 副标题弱化（辅助提示） */
.section-sub { margin:0; font-size:11.5px; line-height:1.45; color:#94a3b8; font-weight:500; }
.grid-two { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:1rem 1.15rem; }
.field-block { display:flex; flex-direction:column; gap:.45rem; font-size:13px; font-weight:600; color:#374151; }
.field-block input,.field-block select { padding:.7rem .85rem; border:1px solid #d1d5db; border-radius:12px; font-size:14px; background:#fff; transition:all .22s cubic-bezier(.4,0,.2,1); font-weight:500; }
.field-block input.readonly { background:#f1f5f9; cursor:not-allowed; opacity:.75; }
.cost-config-bar { display:flex; align-items:flex-start; justify-content:flex-start; gap:.75rem; position:relative; min-height:54px; }
.mode-radios { display:flex; gap:.6rem; }
.pill-radio { border:2px solid #e5e7eb; background:#fff; padding:.55rem 1.05rem; border-radius:26px; font-size:13px; cursor:pointer; line-height:1; transition:all .3s cubic-bezier(.4,0,.2,1); font-weight:600; }
.pill-radio.active { background:linear-gradient(135deg,#3b82f6,#2563eb); color:#fff; border-color:#2563eb; box-shadow:0 4px 12px -1px rgba(59,130,246,.4); }
.pill-radio.count.active { background:linear-gradient(135deg,#fef3c7,#fde68a); border-color:#fcd34d; color:#92400e; }
.amount-focus.inline { position:absolute; top:0; left:66%; transform:translateX(-50%); min-width:170px; border-radius:14px; padding:.65rem .9rem; display:flex; flex-direction:column; align-items:flex-end; gap:.2rem; box-shadow:0 4px 14px -4px rgba(59,130,246,.28); }
.amount-focus.inline.mode-count { background:linear-gradient(135deg,#fef3c7,#fde68a); border:1px solid #fcd34d; }
.amount-focus.inline.mode-fixed { background:linear-gradient(135deg,#eff6ff,#ffffff); border:1px solid #93c5fd; }
.focus-label { font-size:11px; font-weight:700; letter-spacing:.05em; }
.focus-value { font-size:26px; font-weight:700; letter-spacing:-.02em; }
.amount-row.single-line { margin-top:.4rem; }
.input-affix { position:relative; }
.input-affix .prefix { position:absolute; left:.65rem; top:50%; transform:translateY(-50%); font-size:12px; color:#64748b; font-weight:600; }
.input-affix input { padding:.6rem .65rem .6rem 1.55rem; font-size:14px; font-weight:600; }
.attr-config-body { display:flex; gap:1rem; flex-wrap:wrap; align-items:center; }
.attr-mini-btn { display:inline-flex; align-items:center; gap:.55rem; padding:.65rem 1rem; background:linear-gradient(135deg,#ffffff,#f8fafc); border:1px solid #cbd5e1; border-radius:12px; font-size:13px; font-weight:600; color:#374151; cursor:pointer; transition:all .28s cubic-bezier(.4,0,.2,1); }
.attr-mini-btn.disabled { opacity:.5; cursor:not-allowed; }
.attr-mini-btn.filled { background:linear-gradient(135deg,#ecfdf5,#d1fae5); border-color:#16a34a; color:#166534; }
.attr-summary { font-size:11px; color:#94a3b8; font-weight:500; }
.sticky-actions { position:sticky; bottom:0; background:linear-gradient(180deg,rgba(255,255,255,.96),rgba(255,255,255,.99) 70%); display:flex; justify-content:flex-end; align-items:center; gap:.9rem; padding:.7rem 1.4rem .85rem; border-top:1px solid #e2e8f0; }
.actions-btns { display:flex; gap:.85rem; }
.btn { cursor:pointer; border:1px solid #2563eb; background:#3b82f6; color:#fff; padding:.6rem 1.05rem; border-radius:10px; font-size:13px; font-weight:600; letter-spacing:.3px; }
.btn.secondary { background:#fff; color:#2563eb; }
.btn.primary[disabled] { opacity:.5; cursor:not-allowed; }
/* 错误提示样式 */
.error-line { margin:.6rem 0 0; font-size:12px; color:#dc2626; font-weight:600; }
/* 统一关闭按钮样式：旋转 + 粉色过渡 */
.ui-close-btn { background:none; border:1px solid transparent; cursor:pointer; padding:.45rem; border-radius:12px; color:#64748b; transition:all .35s cubic-bezier(.4,0,.2,1); display:flex; align-items:center; justify-content:center; }
.ui-close-btn .icon-x { width:20px; height:20px; }
.ui-close-btn.fancy-rotate:hover { background:linear-gradient(135deg,#fee2e2,#fecaca); color:#dc2626; transform:rotate(90deg); box-shadow:0 4px 14px -4px rgba(220,38,38,.35); }

/* 低窗口高度回退：启用内部滚动，防止溢出遮挡 */
@media (max-height: 760px) {
  .dialog-shell { max-height:calc(100dvh - 4rem); }
  .dialog-content-scroll { overflow-y:auto; }
}
</style>